using System;
using System.Collections.Generic;
using System.Text;

namespace NTrace
{
  public class ClassicProvider : System.Runtime.ConstrainedExecution.CriticalFinalizerObject, IDisposable
  {
    private static readonly Guid simpleMessageGuid = new Guid("F8233B81-3987-44c0-BDBD-219C5BC45B61");

    private const ulong EmptyHandle = 0;
    private const ulong InvalidHandle = (ulong)0xFFFFFFFFFFFFFFFF;

    private readonly Object synchronizer = new Object();
    // It's quite annoying that we can't use a SafeHandle for this; unfortunately,
    // there is no way for custom marshaling (that SafeHandle relies upon) to
    // marshal value types.
    private ulong handle;
    private ulong sessionHandle;
    private EtwTraceFlag traceFlags;
    private EtwTraceLevel traceLevel;
    private bool enabled;

    public ClassicProvider(Guid providerId)
    {
      lock (this.synchronizer)
      {
        if (this.handle == ClassicProvider.EmptyHandle)
        {
          TraceGuidRegistration traceGuid = TraceGuidRegistration.Create(new Guid("{b4955bf0-3af1-4740-b475-99055d3fe9aa}"));

          if (NativeMethods.Success != NativeMethods.RegisterTraceGuids(
              new NativeMethods.ControlCallback(this.ControlCallback),
              IntPtr.Zero,
              ref providerId,
              1,
              ref traceGuid,
              null,
              null,
              out this.handle))
          {
            throw new System.ComponentModel.Win32Exception();
          }
        }
      }
    }

    ~ClassicProvider()
    {
      this.Dispose();
    }

    public bool Enabled
    {
      get
      {
        return this.enabled;
      }
    }

    public EtwTraceFlag Flags
    {
      get
      {
        return this.traceFlags;
      }
    }

    public EtwTraceLevel Level
    {
      get
      {
        return this.traceLevel;
      }
    }

    public void Dispose()
    {
      lock (this.synchronizer)
      {
        if (this.handle != ClassicProvider.EmptyHandle)
        {
          NativeMethods.UnregisterTraceGuids(this.handle);
          this.handle = ClassicProvider.EmptyHandle;
        }
      }
    }

    private int ControlCallback(
      TraceEnableState enableState,
      IntPtr context,
      IntPtr reserved,
      TraceHeader buffer)
    {
      ulong handle = NativeMethods.GetTraceLoggerHandle(buffer);

      if (handle == ClassicProvider.InvalidHandle)
      {
        throw new System.ComponentModel.Win32Exception();
      }

      this.sessionHandle = handle;

      this.traceFlags = (EtwTraceFlag)NativeMethods.GetTraceEnableFlags(this.sessionHandle);
      this.traceLevel = (EtwTraceLevel)NativeMethods.GetTraceEnableLevel(this.sessionHandle);

      if (enableState == TraceEnableState.Enabled)
      {
        this.enabled = true;
      }
      else
      {
        this.enabled = false;
      }

      return 0;
    }

    public void Trace(String formatString, params Object[] args)
    {
      this.Trace(EtwTraceFlag.Diagnostic, EtwTraceLevel.Information, formatString, args);
    }

    public void Trace(EtwTraceFlag traceFlag, String formatString, params Object[] args)
    {
      this.Trace(traceFlag, EtwTraceLevel.Information, formatString, args);
    }

    public void Trace(EtwTraceFlag traceFlag, EtwTraceLevel traceLevel, String formatString, params Object[] args)
    {
      if (formatString == null) throw new ArgumentNullException("formatString");

      // Check to see whether a trace session is active
      // and whether the supplied flags and level 
      if (!this.enabled ||
          (this.Flags & traceFlags) != traceFlags ||
          this.Level <= traceLevel)
      {
        return;
      }

      using (TraceMessageBuilder builder = new TraceMessageBuilder())
      {
        builder.MessageGuid = ClassicProvider.simpleMessageGuid;
        builder.MessageNumber = 10; // All simple trace messages will use this format string
        builder.AddStringArgument(String.Format(formatString, args));

        TraceEventErrorCode result = NativeMethods.TraceEvent(this.sessionHandle, builder.ToPointer());
        if (result != TraceEventErrorCode.Success)
        {
          switch (result)
          {
            case TraceEventErrorCode.OutOfMemory:
              throw new OutOfMemoryException();
            case TraceEventErrorCode.InvalidParamter:
            case TraceEventErrorCode.MoreData:
            case TraceEventErrorCode.InvalidFlagNumber:
            case TraceEventErrorCode.InvalidHandle:
              throw new System.ComponentModel.Win32Exception((int)result);
          }
        }
      }
    }

    public void Trace(Guid messageGuid, int messageId, params Object[] args)
    {
      if (!this.enabled) return;

      using (TraceMessageBuilder builder = new TraceMessageBuilder())
      {
        builder.MessageGuid = messageGuid;
        builder.MessageNumber = messageId;
        for (int index = 0; index < args.Length; index++)
        {
          String stringArgument = args[index] as String;
          if (stringArgument == null && args[index] != null)
          {
            // TODO: include a ITraceFormatter check and prefer that over ToString();
            stringArgument = args[index].ToString();
          }
          builder.AddStringArgument(stringArgument);
        }

        TraceEventErrorCode result = NativeMethods.TraceEvent(this.sessionHandle, builder.ToPointer());
        if (result != TraceEventErrorCode.Success)
        {
          switch (result)
          {
            case TraceEventErrorCode.OutOfMemory:
              throw new OutOfMemoryException();
            case TraceEventErrorCode.InvalidParamter:
            case TraceEventErrorCode.MoreData:
            case TraceEventErrorCode.InvalidFlagNumber:
            case TraceEventErrorCode.InvalidHandle:
              throw new System.ComponentModel.Win32Exception((int)result);
          }
        }
      }
    }
  }
}
